refactor(runtime): PermissionDeniedError has ONE declaration again (#7270) - #7462
Conversation
…7270) `security/resolve-execution-context.ts` re-declared `PermissionDeniedError` and `isPermissionDeniedError` character-for-character from `@objectstack/plugin-security`'s `errors.ts`, with a doc comment asking the next editor to keep them "structurally identical" and nothing enforcing it. Both fields of the ADR-0112 denial envelope are load-bearing — `statusCode` is what the dispatcher answers with, `code` is what a matcher keys on — so editing one copy's `403` left every test in the repo green while one dispatch path answered a denial with the wrong status. `@objectstack/plugin-security` throws these (23 call sites); the runtime only catches them. The plugin now owns the single declaration and the runtime module re-exports it. `@objectstack/plugin-security` was already a plain `dependencies` entry of `@objectstack/runtime`, so this adds no dependency, and tsup externalizes workspace dependencies — the bundle gained an `import "@objectstack/plugin-security"` and lost the duplicated class (ESM 428.21 KB -> 428.02 KB). The symbols stay exported from the runtime module rather than being deleted, because `http-dispatcher.ts` imports `isPermissionDeniedError` from that path. Nothing outside the package is affected: `security/index.ts` never re-exported either symbol, so neither was reachable from the public barrel. The matcher is unchanged and stays duck-typed (`name` / `code` / message-prefix, never `instanceof`), which is what makes the re-export safe: dual CJS/ESM output and bundling can still hand the two sides distinct class objects. The new `permission-denied-error-parity.test.ts` pins both halves — that the two import paths reach the same declaration (this assertion fails against the old copy), and that an instance built from a deliberately foreign class of the same shape is still matched. No behaviour change: `name`, `code: 'PERMISSION_DENIED'` and `statusCode: 403` are byte-identical to what the runtime copy produced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015TbH9juzW7PvJzbsdpUnEp
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 20 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #7270
security/resolve-execution-context.tsre-declaredPermissionDeniedErrorandisPermissionDeniedErrorcharacter-for-character from@objectstack/plugin-security'serrors.ts, with a doc comment asking the next editor to keep them "structurally identical" and nothing enforcing it. Both fields of the ADR-0112 denial envelope are load-bearing —statusCodeis what the dispatcher answers with,codeis what a matcher keys on — so editing one copy's403left every test in the repo green while one dispatch path answered a denial with the wrong status.Route taken: (a), single declaration
@objectstack/plugin-securitythrows these (23 call sites); the runtime only catches them. The plugin now owns the single declaration and the runtime module re-exports it.@objectstack/plugin-securitywas already a plaindependenciesentry of@objectstack/runtime, so this adds no dependency edge, and tsup externalizes workspace dependencies — the bundle gained animport "@objectstack/plugin-security"and lost the duplicated class (ESM 428.21 KB → 428.02 KB).The symbols stay exported from the runtime module rather than being deleted, because
http-dispatcher.tsimportsisPermissionDeniedErrorfrom that path. Nothing outside the package is affected:security/index.tsnever re-exported either symbol, so neither was reachable from the public barrel.Why the re-export is safe
The matcher is unchanged and stays duck-typed (
name/code/ message-prefix, neverinstanceof) — which is exactly what makes the re-export safe: dual CJS/ESM output and bundling can still hand the two sides distinct class objects. The card's note about the prose-match arm stands as recorded; this PR does not touch it.The new
permission-denied-error-parity.test.tspins both halves — that the two import paths reach the same declaration (this assertion fails against the old copy), and that an instance built from a deliberately foreign class of the same shape is still matched.No behaviour change:
name,code: 'PERMISSION_DENIED'andstatusCode: 403are byte-identical to what the runtime copy produced.Implemented by a dispatched dev agent in its own container; PR opened by the PM seat because the container had git push but no GitHub API access. Full rationale is preserved verbatim in the commit message of
e775a58bb.Generated by Claude Code